// rom loader module, added by regret

/* changelog:
 update 1: create (from barchive)
*/

#include "burner.h"
#include "romloader.h"
#include "seldef.h"

RomLoader romLoader;

// RomLoader
RomLoader::RomLoader() :
	archiveError(0),
	romFind(NULL),
	romCount(0),
	totalSize(0),
	arcList(NULL),
	listCount(0),
	currentArc(-1),
	burnInterfaceFlag(false)
{
	for (unsigned i = 0; i < BZIP_MAX; i++) {
		archiveName[i] = NULL;
	}
}

RomLoader::~RomLoader()
{
	clearArchiveList();
}

// private functions
void RomLoader::clearArchiveList()
{
	if (arcList) {
		for (unsigned i = 0; i < listCount; i++) {
			delete [] arcList[i].szName;
			arcList[i].szName = NULL;
		}
		delete [] arcList;
		arcList = NULL;
	}

	listCount = 0;
}

int RomLoader::romDescribe(BurnRomInfo* pri)
{
	if (!pri) {
		return 1;
	}

	if (archiveError == 0) {
		archiveError |= 0x8000;
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_INVALID));
	}

	FBAPopupAddText(PUF_TEXT_DEFAULT, L" " SEPERATOR_1);
	if (pri->nType & BRF_ESS) {
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_ESS));
	}
	if (pri->nType & BRF_BIOS) {
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_BIOS));
	}
	if (pri->nType & BRF_PRG) {
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_PRG));
	}
	if (pri->nType & BRF_GRA) {
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_GRA));
	}
	if (pri->nType & BRF_SND) {
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_SND));
	}
	FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_ROM));

	return 0;
}

int RomLoader::getError(int state)
{
	switch (state) {
		case STAT_OK:			// OK
			return 0x0000;
		case STAT_NOFIND:		// Not present
			return 0x0001;
		case STAT_SMALL:		// Incomplete
			return 0x0001;
		default:				// CRC wrong or too large
			return 0x0100;
	}

	return 0x0100;
}

// Check the roms to see if the code, graphics etc are complete
int RomLoader::checkInternal()
{
	BurnRomInfo ri;
	int state = STAT_NOFIND;
	int error;

	for (int i = 0; i < romCount; i++) {
		memset(&ri, 0, sizeof(ri));
		BurnDrvGetRomInfo(&ri, i);			// Find information about the wanted rom

		if (!(ri.nType & BRF_OPT) && (ri.nCrc != 0)) {
			state = romFind[i].state;	// Get the state of the rom in the archive file
			error = getError(state);

			if (state == STAT_NOFIND && ri.nType) {	// (A type of 0 means empty slot - no rom)
				char* szName = "Unknown";
				romDescribe(&ri);
				BurnDrvGetRomName(&szName, i, 0);
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_NOTFOUND), szName);
			}

			if (error == 0) {
				archiveError |= 0x2000;
			}

			if (ri.nType & BRF_ESS) {		// essential rom - without it the game may not run at all
				archiveError |= error << 0;
			}
			if (ri.nType & BRF_PRG) {		// rom which contains program information
				archiveError |= error << 1;
			}
			if (ri.nType & BRF_GRA) {		// rom which contains graphics information
				archiveError |= error << 2;
			}
			if (ri.nType & BRF_SND) {		// rom which contains sound information
				archiveError |= error << 3;
			}
		}
	}

	if (archiveError & 0x0F0F) {
		archiveError |= 0x4000;
	}

	return 0;
}

// public functions
int RomLoader::burnLoadRom(unsigned char* dest, unsigned* wrote, unsigned i)
{
	if (i >= romCount || !romFind) {
		return 1;
	}

	BurnRomInfo ri;
	memset(&ri, 0, sizeof(ri));
	BurnDrvGetRomInfo(&ri, i); // Get info

	// show what we're doing
	char* pszRomName = NULL;
	BurnDrvGetRomName(&pszRomName, i, 0);
	if (pszRomName == NULL) {
		pszRomName = "unknown";
	}

	wchar_t szText[MAX_PATH];
	swprintf(szText, L"Loading");

	if (ri.nType & (BRF_PRG | BRF_GRA | BRF_SND | BRF_BIOS)) {
		if (ri.nType & BRF_BIOS) {
			swprintf (szText + wcslen(szText), L" %s", L"BIOS ");
		}
		if (ri.nType & BRF_PRG) {
			swprintf (szText + wcslen(szText), L" %s", L"program ");
		}
		if (ri.nType & BRF_GRA) {
			swprintf (szText + wcslen(szText), L" %s", L"graphics ");
		}
		if (ri.nType & BRF_SND) {
			swprintf (szText + wcslen(szText), L" %s", L"sound ");
		}
		swprintf(szText + wcslen(szText), L"(%hs)...", pszRomName);
	} else {
		swprintf(szText + wcslen(szText), L" %hs...", pszRomName);
	}
	progressUpdateBurner(ri.nLen ? 1.0 / ((double)totalSize / ri.nLen) : 0, szText, 0);

#if defined (_WIN32)
	// Check for messages:
	MSG msg;
	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
		DispatchMessage(&msg);
	}
#endif

	// skip loading optional rom
	if (ri.nType & BRF_OPT) {
		return 0;
	}

	if (romFind[i].state == STAT_NOFIND) {	// Rom not found in archive at all
		return 1;
	}

	int wantZip = romFind[i].archive;		// Which archive file it is in
	if (currentArc != wantZip) {			// If we haven't got the right archive file currently open
		archiveLoad.close();
		currentArc = -1;

		if (archiveLoad.open(WtoA(archiveName[wantZip]))) {
			return 1;
		}

		currentArc = wantZip;
	}

	// Read in file and return how many bytes we read
	if (archiveLoad.loadFile(dest, ri.nLen, romFind[i].pos, wrote)) {
		// Error loading from the archive file
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DISK),
						pszRomName, getFilenameW(archiveName[currentArc]));
		FBAPopupDisplay(PUF_TYPE_WARNING);
		return 1;
	}

	return 0;
}

// Catch calls to BurnLoadRom() once the emulation has started;
// Intialise the zip module before forwarding the call, and exit cleanly.
int RomLoader::burnerLoadRom(unsigned char* dest, unsigned* wrote, unsigned i)
{
	int ret;

	open();

	if ((ret = loadRom(dest, wrote, i)) != 0) {
		char* filename;
		BurnDrvGetRomName(&filename, i, 0);
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_REQUEST), filename, BurnDrvGetTextA(DRV_NAME));
		FBAPopupDisplay(PUF_TYPE_ERROR);
	}

	close();

	burnInterfaceFlag = false;

	return ret;
}

int RomLoader::loadRom(unsigned char* dest, unsigned* wrote, unsigned i)
{
	if (burnInterfaceFlag) {
		return burnLoadRom(dest, wrote, i);
	} else {
		return burnerLoadRom(dest, wrote, i);
	}
}

int RomLoader::loadOneRom(const char* arcName, const char* fileName, void** dest, unsigned* wrote)
{
	return archiveLoad.loadOneFile(arcName, fileName, dest, wrote);
}

void RomLoader::switchInterface()
{
	burnInterfaceFlag = false;
}

int RomLoader::status()
{
	if (!(archiveError & 0x0F0F)) {
		FBAPopupDisplay(PUF_TYPE_INFO | PUF_TYPE_LOGONLY);
		return RL_STATUS_OK;
	}
	if (archiveError & 1) {
		FBAPopupDisplay(PUF_TYPE_ERROR);
		return RL_STATUS_ERROR;
	}

	FBAPopupDisplay(PUF_TYPE_WARNING);
	return RL_STATUS_BADDATA;
}

int RomLoader::check()
{
	// Check the roms to see if the code, graphics etc are complete
	checkInternal();

	if (archiveError & 0x2000) {
		if (!(archiveError & 0x0F0F)) {
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_OK));
		} else {
			FBAPopupAddText(PUF_TEXT_DEFAULT, L"\n");
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_PROBLEM));
		}

		if (archiveError & 0x0101) {
			FBAPopupAddText(PUF_TEXT_DEFAULT, L"\n " SEPERATOR_1);
			if (archiveError & 0x0001) {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_ESS_MISS));
			} else {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_ESS_BAD));
			}
		}
		if (archiveError & 0x0202) {
			FBAPopupAddText(PUF_TEXT_DEFAULT, L"\n " SEPERATOR_1);
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_PRG));
			if (archiveError & 0x0002) {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DATA_MISS));
			} else {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DATA_BAD));
			}
		}
		if (archiveError & 0x0404) {
			FBAPopupAddText(PUF_TEXT_DEFAULT, L"\n " SEPERATOR_1);
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_GRA));
			if (archiveError & 0x0004) {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DATA_MISS));
			} else {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DATA_BAD));
			}
		}
		if (archiveError & 0x0808) {
			FBAPopupAddText(PUF_TEXT_DEFAULT, L"\n " SEPERATOR_1);
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DET_SND));
			if (archiveError & 0x0008) {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DATA_MISS));
			} else {
				FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DATA_BAD));
			}
		}

		// Catch non-categorised ROMs
		if ((archiveError & 0x0F0F) == 0) {
			if (archiveError & 0x0010) {
				FBAPopupAddText(PUF_TEXT_DEFAULT, L"\n " SEPERATOR_1);
				if (archiveError & 0x1000) {
					FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DATA_MISS));
				} else {
					FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_DATA_BAD));
				}
			}
		}
	} else {
		FBAPopupAddText(PUF_TEXT_DEFAULT, L"\n");
		FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_NODATA));
	}

	burnInterfaceFlag = true; // Okay to call our function to load each rom

	return 0;
}

int RomLoader::open()
{
	if (archiveName == NULL) {
		return 1;
	}

	close(); // Make sure nothing is open

	// Count the number of roms needed
	for (romCount = 0; ; romCount++) {
		if (BurnDrvGetRomInfo(NULL, romCount)) {
			break;
		}
	}
	if (romCount <= 0) {
		return 1;
	}

	// Create an array for holding lookups for each rom -> archive entries
	unsigned int nMemLen = romCount * sizeof(ROMFIND);
	romFind = new ROMFIND[nMemLen];
	if (romFind == NULL) {
		return 1;
	}
	memset(romFind, 0, nMemLen);

	// Locate each archive file
	bool bFound = false;
	int checkvalue = ArchiveLoad::ARC_NONE;
	char szFullName[MAX_PATH] = "";
	char* szName = NULL;

	for (int y = 0, z = 0; y < BZIP_MAX && z < BZIP_MAX; y++) {
		// Get archive name without extension
		if (BurnGetArchiveName(&szName, y, false)) {
			break;
		}

		bFound = false;

		for (int d = 0; d < DIRS_MAX; d++) {
//			if (bFound) {
//				break;
//			}

			if (!_wcsicmp(appRomPaths[d], L"")) {
				continue; // skip empty path
			}

			// check the archived rom file, modified by regret
			sprintf(szFullName, "%s%s", WtoA(appRomPaths[d]), szName);

			checkvalue = archiveLoad.check(szFullName, (loadMenuShowX & CHECK7ZIP) ? 0 : 1);
			if (checkvalue == ArchiveLoad::ARC_NONE) {
				continue;
			}

			bFound = true;

			archiveName[z] = new wchar_t[MAX_PATH];
			if (!archiveName[z]) {
				continue;
			}
			wcscpy(archiveName[z], AtoW(szFullName));
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_FOUND), szName, archiveName[z]);
			z++;
		}

		if (!bFound) {
			FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_NOTFOUND), szName);
		}
	}

	FBAPopupAddText(PUF_TEXT_DEFAULT, L"\n");

	// Locate the ROM data in the archive files
	int nFind = -1;
	BurnRomInfo ri;

	for (int z = 0; z < BZIP_MAX; z++) {
		if (!archiveName[z]) {
			continue;
		}

		if (archiveLoad.open(WtoA(archiveName[z]))) {
			continue;
		}
		archiveLoad.getList(&arcList, &listCount);	// Get the list of entries

		currentArc = z;

		for (int i = 0; i < romCount; i++) {
			if (romFind[i].state == STAT_OK) {
				continue;					// Already found this and it's okay
			}

			nFind = findRom(i, arcList, listCount, BurnGetDrvSelect());
			if (nFind < 0) {				// Couldn't find this rom at all
				continue;
			}

			romFind[i].archive = z;			// Remember which archive file it is in
			romFind[i].pos = nFind;
			romFind[i].state = STAT_OK;		// Set to found okay

			memset(&ri, 0, sizeof(ri));
			BurnDrvGetRomInfo(&ri, i);		// Get info about the rom

			// if size good & nodump, try to load the file with correct filename
			if (!(ri.nType & BRF_OPT) && (ri.nCrc != 0)) {
				totalSize += ri.nLen;
			}

			if (arcList[nFind].nLen == ri.nLen) {
				if (ri.nCrc) {									// If we know the CRC
					if (arcList[nFind].nCrc != ri.nCrc) {		// Length okay, but CRC wrong
						if (!(loadMenuShowX & DISABLECRC)) {	// disable crc check
							romFind[i].state = STAT_CRC;
						}
					}
				}
			} else {
				if (loadMenuShowX & DISABLECRC) {
					ri.nLen = arcList[nFind].nLen;				// disable size check
				} else {
					if (arcList[nFind].nLen < ri.nLen) {
						romFind[i].state = STAT_SMALL;			// Too small
					} else {
						romFind[i].state = STAT_LARGE;			// Too big
					}
				}
			}

			if (romFind[i].state != STAT_OK) {
				romDescribe(&ri);

				if (romFind[i].state == STAT_CRC) {
					FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_CRC),
						getFilenameA(arcList[nFind].szName), arcList[nFind].nCrc, ri.nCrc);
				}
				if (romFind[i].state == STAT_SMALL) {
					FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_SMALL),
						getFilenameA(arcList[nFind].szName), arcList[nFind].nLen >> 10, ri.nLen >> 10);
				}
				if (romFind[i].state == STAT_LARGE) {
					FBAPopupAddText(PUF_TEXT_DEFAULT, MAKEINTRESOURCE(IDS_ERR_LOAD_LARGE),
						getFilenameA(arcList[nFind].szName), arcList[nFind].nLen >> 10, ri.nLen >> 10);
				}
			}
		}

		archiveLoad.close();

		clearArchiveList(); // Close the last archive file if open
		currentArc = -1;
	}

	return check();
}

int RomLoader::close()
{
	archiveLoad.close();
	currentArc = -1;		// Close the last archive file if open

	archiveError = 0;		// reset romset errors
	totalSize = 0;

	if (romFind) {
		delete [] romFind;
		romFind = NULL;
	}
	romCount = 0;

	for (int z = 0; z < BZIP_MAX; z++) {
		delete [] archiveName[z];
		archiveName[z] = NULL;
	}

	return 0;
}
